package GUI;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.*;

import java.awt.image.*;

import DAL.*;
import DTO.*;

/**
 * Classe responsável pela apresentação da área gráfica do jogo
 *
 * @author (R. Soares)
 * @version (1.0 - 24.05.2010)
 */
public class TelaJogo
{
    // Declaração da classe TelaJogo para o padrão Singleton.
    private static TelaJogo telaJogoSingleton;

    /**
     * Método para retornar o Objeto TelaJogo (Necessário para o padrão Singleton).
     */
    public static TelaJogo retornaTela()
    {
        if(telaJogoSingleton == null)
        {
            ConfiguracaoDTO configuracao = new ConfiguracaoDAL().obterConfiguracao();

            BufferedImage background = new ArquivoImagemDAL().getImagemFundo();

            telaJogoSingleton = new TelaJogo("2012 - O Jogo",
                                                                    configuracao.getLarguraTela(),
                                                                    configuracao.getAlturaTela(), Color.black,
                                                                    background);
        }

        telaJogoSingleton.setVisivel(true);
        return telaJogoSingleton;
    }

    // Variáveis de Instância --------------------------------------------------*
    private JFrame frame;
    private CanvasPane canvas;
    private Graphics2D graphic;
    private Color backgroundColour;
    private Image canvasImage;

    private BufferedImage background;


    //Listas de objetos presentes na tela
    private List objects;
    private HashMap shapes;

    private List textObjects;
    private HashMap textos;

    // Lista de Teclas de comandos do jogo
    private boolean teclaAcima;
    private boolean teclaAbaixo;
    private boolean teclaDireita;
    private boolean teclaEsquerda;
    private boolean teclaEspaco;
    private boolean teclaEnter;

    //Variáveis para texto
    private String texto;
    private Font font;
    private int coordenadaX;
    private int coordenadaY;


    // Contrutores -------------------------------------------------------------- *
    /**
     * O construtor da classe recebe o título, as dimensões e o fundo da área da tela.
     *
     * @param nomeTela  Titulo que será apresentado na Tela
     * @param largura   Largura da Tela
     * @param altura    Altura da Tela
     * @param bgColor   Cor de Fundo da Tela (*SERÁ ALTERADO PARA IMAGEM DE FUNDO*)
     */
    private TelaJogo(String nomeTela, int largura, int altura, Color bgColor, BufferedImage background)
    {
        frame = new JFrame();
        canvas = new CanvasPane();
        frame.setContentPane(canvas);
        frame.setTitle(nomeTela);
        canvas.setPreferredSize(new Dimension(largura, altura));
        backgroundColour = bgColor;
        frame.pack();
        objects = new ArrayList();
        shapes = new HashMap();

        textObjects = new ArrayList();
        textos = new HashMap();

        this.background = background;

        // Desabilita a caracteristica de redimencionamento da tela.
        frame.setResizable(false);

        // Posiciona a janela no centro da tela
        frame.setLocationRelativeTo(null);

        // Habilita a caracteristicva de: Ao clicar no fechar janela, encerra o programa
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // inicializa teclas
        teclaAcima  = false;
        teclaAbaixo = false;
        teclaDireita = false;
        teclaEsquerda = false;
        teclaEspaco = false;
        teclaEnter = false;

        //Inicia as funções de comando de teclas
        InicializaFuncoesTecla();
    }


    // Métodos ---------------------------------------------------------------------*

    /**
     * Defini a área de jogo como visivel e a traz para a frente da tela. Esse método
     * também pode ser usado para trazer a área já visível para a frente de outras
     * janelas.
     *
     * @param visibilidade  valor boolean representando a visibilidade deejada da área
     *                      (true: visível,  false : invisivel)
     */
    public void setVisivel(boolean visibilidade)
    {
        if(graphic == null) {
            // Na primeira chamada: instancia as imagens e preenche a cor de fundo.

            Dimension dimensoes = canvas.getSize();
            canvasImage = canvas.createImage(dimensoes.width, dimensoes.height);
            graphic = (Graphics2D) canvasImage.getGraphics();
            graphic.setColor(backgroundColour);


            graphic.fillRect(0, 0, dimensoes.width, dimensoes.height);
            graphic.setColor(Color.black);
        }
        frame.setVisible(visibilidade);
    }

    /**
     * Insere (desenha) um Shape fornecido na área (canvas)
     *
     * @param  referenceObject  um objeto para definir a identificação do shape
     * @param  cor           a cor do shape
     * @param  shape      a forma do object que será inserido na área (canvas)
     */
    public void draw(Object referenceObject, Shape shape, TexturePaint imagePaint)
    {
        objects.remove(referenceObject);   // aplicado no caso do objeto já existir na lista
        objects.add(referenceObject);      // adiciona ao fim da lista
        shapes.put(referenceObject, new ShapeDescription(shape, imagePaint) );
        redraw();
    }

    /**
     * Inclui um novo texto a área da tela
     *
     * @param  referenceObject : Objeto do texto que deve ser escrito
     */
    public void drawTexto(Object referenceObject, String texto, Font font, int pX, int pY)
    {
        textObjects.remove(referenceObject);   // aplicado no caso do texto já existir na lista
        textObjects.add(referenceObject);      // adiciona ao fim da lista
        textos.put( referenceObject, new TextDescription(texto, font, pX, pY ) );
        redraw();
    }

    /**
     * Apaga uma imagem da área. O objeto da imegam deve ser fornecido.
     * @param  referenceObject  the shape object to be erased
     */
    public void apagarImagem(Object referenceObject)
    {
        objects.remove(referenceObject);   // aplicado no caso do objeto já existir na lista
        shapes.remove(referenceObject);
        redraw();
    }

    /**
     * Apaga uma imagem da área. O objeto da imegam deve ser fornecido.
     * @param  referenceObject  the shape object to be erased
     */
    public void apagarTexto(Object referenceObject)
    {
        textObjects.remove(referenceObject);   // aplicado no caso do objeto já existir na lista
        textos.remove(referenceObject);
        redraw();
    }

    /**
     * Aguarda por uma quantidade especificada de millisegundos antes de finalizar.
     * Método empregado para a execução de animações.
     *
     * @param  millisegundos : quantidade de tempo em milisegundos
     */
    public void aguardar(int milisegundos)
    {
        try
        {
            Thread.sleep(milisegundos);
        }
        catch (Exception e)
        {
            // excessão ignorada.
        }
    }

    /**
     * Redesenha todos os elementos da área de tela.
     */
    private void redraw()
    {
        apagar();

        for(Iterator i = objects.iterator(); i.hasNext(); )
        {
            ( (ShapeDescription)shapes.get(i.next() ) ).draw(graphic);
        }

        for(Iterator j = textObjects.iterator(); j.hasNext(); )
        {
            ( (TextDescription) textos.get( j.next() ) ).draw(graphic);
        }


        canvas.repaint();
    }

    /**
     * Apaga toda a área da tela. (Não a repinta)
     */
    private void apagar()
    {
        Color original = graphic.getColor();
        graphic.setColor(backgroundColour);
        Dimension size = canvas.getSize();
        graphic.fill(new Rectangle(0, 0, size.width, size.height));
        graphic.setColor(original);
    }

    /**
     * Apaga a Tela
     */
    public void apagarTela()
    {
         frame.dispose();

          ConfiguracaoDTO configuracao = new ConfiguracaoDAL().obterConfiguracao();

          BufferedImage background = new ArquivoImagemDAL().getImagemFundo();

          telaJogoSingleton = new TelaJogo("2012 - O Jogo",
                                                                    configuracao.getLarguraTela(),
                                                                    configuracao.getAlturaTela(), Color.black,
                                                                    background);

        telaJogoSingleton.setVisivel(true);
    }


    //**********************************************************************************************
    /*
     * Inner class CanvasPane - o atual componente canvas contido no Canvas
     * frame. É necessário ser adicionado um JPanel para que seja atualizada
     * a imagem sobre ele.
     */

    private class CanvasPane extends JPanel
    {
        public void paint(Graphics g)
        {
            g.drawImage(canvasImage, 0, 0, null);
        }
    }

    //***********************************************************************************************
    private class ShapeDescription
    {
        private Shape shape;
        private TexturePaint imagePaint;

        public ShapeDescription(Shape shape, TexturePaint imagePaint)
        {
            this.shape = shape;
            this.imagePaint = imagePaint;
        }

        public void draw(Graphics2D graphic)
        {
            graphic.setColor(Color.black);

            if(imagePaint != null)
                    graphic.setPaint(imagePaint);

            graphic.fill(shape);
        }
    }

    //***********************************************************************************************
    private class TextDescription
    {
        private Font fonte ;
        private String texto;
        private int pX;
        private int pY;

        public TextDescription(String texto, Font fonte, int pX, int pY)
        {
            this.texto = texto;
            this.fonte = fonte;
            this.pX = pX;
            this.pY = pY;
        }

        public void draw(Graphics2D graphic)
        {
            graphic.setColor( Color.WHITE );
            graphic.setFont( fonte );
            graphic.drawString(texto, pX, pY);
        }
    }

    // ********************  Métodos para Funções de comandos de Teclado  **********************

    /**
     * Método para adição do "Listener" de teclado a tela de jogo (frame). O metodo seta
     * cada uma das variáveis de teclas de jogo conforme são acionadas.
     */
    private void InicializaFuncoesTecla()
    {
            frame.addKeyListener(new KeyAdapter() {

                 public void keyPressed(KeyEvent ev) {

                     switch(ev.getKeyCode())
                     {
                         case 38:  // Tecla seta para cima - cod 38
                            teclaAcima = true;
                            break;

                         case 40:  // Tecla seta para baixo - cod 40
                            teclaAbaixo = true;
                            break;

                         case 39:  // Tecla seta para direita - cod 39
                            teclaDireita = true;
                            break;

                         case 37:  // Tecla seta para esquerda - cod 37
                            teclaEsquerda = true;
                            break;

                         case 32:  // Tecla seta para espaco - cod 32
                            teclaEspaco = true;
                            break;

                         case 10:  // Tecla seta para enter - cod 10
                            teclaEnter = true;
                            break;
                    }
                 }
            });

            // coloca o focus de ação para a tela de modo a possibilitar que a mesma receba
            // os comandos.
            frame.requestFocusInWindow();
     }

    /**
     * Método que retorna o atual estado do botão seta "para cima". Caso o botão tenha sido
     * acionado (retorna true), será efetuado o reset do estado do botão (torna false).
     *
     * @return  boolean : true - Botão acionado; false - botão não acionado
     */
    public boolean retornaBotaoAcima()
    {
            boolean tecla = teclaAcima;
            teclaAcima = false;
            return tecla;
    }

    /**
     * Método que retorna o atual estado do botão seta "para baixo". Caso o botão tenha sido
     * acionado (retorna true), será efetuado o reset do estado do botão (torna false).
     *
     * @return  boolean : true - Botão acionado; false - botão não acionado
     */
    public boolean retornaBotaoAbaixo()
    {
            boolean tecla = teclaAbaixo;
            teclaAbaixo = false;
            return tecla;
    }

    /**
     * Método que retorna o atual estado do botão seta "para direita". Caso o botão tenha sido
     * acionado (retorna true), será efetuado o reset do estado do botão (torna false).
     *
     * @return  boolean : true - Botão acionado; false - botão não acionado
     */
    public boolean retornaBotaoDireita()
    {
            boolean tecla = teclaDireita;
            teclaDireita = false;
            return tecla;
    }

    /**
     * Método que retorna o atual estado do botão seta "para esquerda". Caso o botão tenha sido
     * acionado (retorna true), será efetuado o reset do estado do botão (torna false).
     *
     * @return  boolean : true - Botão acionado; false - botão não acionado
     */
    public boolean retornaBotaoEsquerda()
    {
            boolean tecla = teclaEsquerda;
            teclaEsquerda = false;
            return tecla;
    }

    /**
     * Método que retorna o atual estado do botão "espaço". Caso o botão tenha sido
     * acionado (retorna true), será efetuado o reset do estado do botão (torna false).
     *
     * @return  boolean : true - Botão acionado; false - botão não acionado
     */
    public boolean retornaBotaoEspaco()
    {
            boolean tecla = teclaEspaco;
            teclaEspaco = false;
            return tecla;
    }

    /**
     * Método que retorna o atual estado do botão "enter". Caso o botão tenha sido
     * acionado (retorna true), será efetuado o reset do estado do botão (torna false).
     *
     * @return  boolean : true - Botão acionado; false - botão não acionado
     */
    public boolean retornaBotaoEnter()
    {
            boolean tecla = teclaEnter;
            teclaEnter = false;
            return tecla;
    }

}
